/* This class represents a single display item containing information about an application.
*
* appInfo - AppInfo object containing information about the application
- * availableWidth - total width available for the item
*/
-function AppDisplayItem(appInfo, availableWidth) {
- this._init(appInfo, availableWidth);
+function AppDisplayItem(appInfo) {
+ this._init(appInfo);
}
AppDisplayItem.prototype = {
__proto__: GenericDisplay.GenericDisplayItem.prototype,
- _init : function(appInfo, availableWidth) {
- GenericDisplay.GenericDisplayItem.prototype._init.call(this, availableWidth);
+ _init : function(appInfo) {
+ GenericDisplay.GenericDisplayItem.prototype._init.call(this);
this._appInfo = appInfo;
this._setItemInfo(appInfo.get_name(), appInfo.get_description());
this.actor.append(this._icon, Big.BoxPackFlags.NONE);
this._text = new Clutter.Text({ color: GenericDisplay.ITEM_DISPLAY_NAME_COLOR,
font_name: "Sans 14px",
- ellipsize: Pango.EllipsizeMode.END,
text: name });
// We use individual boxes for the label and the arrow to ensure that they
/* This class represents a display containing a collection of application items.
* The applications are sorted based on their popularity by default, and based on
* their name if some search filter is applied.
- *
- * width - width available for the display
*/
-function AppDisplay(width) {
- this._init(width);
+function AppDisplay() {
+ this._init();
}
AppDisplay.prototype = {
__proto__: GenericDisplay.GenericDisplay.prototype,
- _init : function(width) {
- GenericDisplay.GenericDisplay.prototype._init.call(this, width);
+ _init : function() {
+ GenericDisplay.GenericDisplay.prototype._init.call(this);
this._menus = [];
this._menuDisplays = [];
},
// Creates an AppDisplayItem based on itemInfo, which is expected be an Shell.AppInfo object.
- _createDisplayItem: function(itemInfo, width) {
- return new AppDisplayItem(itemInfo, width);
+ _createDisplayItem: function(itemInfo) {
+ return new AppDisplayItem(itemInfo);
}
};
x_align: Big.BoxAlignment.CENTER });
this._nameBox = nameBox;
- this._wordWidth = Shell.Global.get().get_max_word_width(this.actor, appInfo.get_name(),
- "Sans 12px");
this._name = new Clutter.Text({ color: GenericDisplay.ITEM_DISPLAY_NAME_COLOR,
font_name: "Sans 12px",
line_alignment: Pango.Alignment.CENTER,
--- /dev/null
+/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */
+
+const Big = imports.gi.Big;
+const Clutter = imports.gi.Clutter;
+const Gio = imports.gi.Gio;
+const Gtk = imports.gi.Gtk;
+const Mainloop = imports.mainloop;
+const Shell = imports.gi.Shell;
+const Signals = imports.signals;
+const Lang = imports.lang;
+
+const AppDisplay = imports.ui.appDisplay;
+const DocDisplay = imports.ui.docDisplay;
+const GenericDisplay = imports.ui.genericDisplay;
+const Button = imports.ui.button;
+const Main = imports.ui.main;
+
+const DEFAULT_PADDING = 4;
+const DASH_SECTION_PADDING = 6;
+const DASH_SECTION_SPACING = 12;
+const DASH_CORNER_RADIUS = 5;
+const DASH_SEARCH_BG_COLOR = new Clutter.Color();
+DASH_SEARCH_BG_COLOR.from_pixel(0xffffffff);
+const DASH_SECTION_COLOR = new Clutter.Color();
+DASH_SECTION_COLOR.from_pixel(0x846c3dff);
+const DASH_TEXT_COLOR = new Clutter.Color();
+DASH_TEXT_COLOR.from_pixel(0xffffffff);
+
+const PANE_BORDER_COLOR = new Clutter.Color();
+PANE_BORDER_COLOR.from_pixel(0x213b5dfa);
+const PANE_BORDER_WIDTH = 2;
+
+const PANE_BACKGROUND_COLOR = new Clutter.Color();
+PANE_BACKGROUND_COLOR.from_pixel(0x0d131ff4);
+
+
+function Pane() {
+ this._init();
+}
+
+Pane.prototype = {
+ _init: function () {
+ this._open = false;
+
+ this.actor = new Big.Box({ orientation: Big.BoxOrientation.VERTICAL,
+ background_color: PANE_BACKGROUND_COLOR,
+ border: PANE_BORDER_WIDTH,
+ border_color: PANE_BORDER_COLOR,
+ padding: DEFAULT_PADDING,
+ reactive: true });
+ this.actor.connect('button-press-event', Lang.bind(this, function (a, e) {
+ // Eat button press events so they don't go through and close the pane
+ return true;
+ }));
+
+ let chromeTop = new Big.Box({ orientation: Big.BoxOrientation.HORIZONTAL,
+ spacing: 6 });
+
+ let global = Shell.Global.get();
+ let closeIconUri = "file://" + global.imagedir + "close.svg";
+ let closeIcon = Shell.TextureCache.get_default().load_uri_sync(Shell.TextureCachePolicy.FOREVER,
+ closeIconUri,
+ 16,
+ 16);
+ closeIcon.reactive = true;
+ closeIcon.connect('button-press-event', Lang.bind(this, function (b, e) {
+ this.close();
+ return true;
+ }));
+ chromeTop.append(closeIcon, Big.BoxPackFlags.END);
+ this.actor.append(chromeTop, Big.BoxPackFlags.NONE);
+
+ this.content = new Big.Box({ orientation: Big.BoxOrientation.VERTICAL,
+ spacing: DEFAULT_PADDING });
+ this.actor.append(this.content, Big.BoxPackFlags.EXPAND);
+
+ // Hidden by default
+ this.actor.hide();
+ },
+
+ open: function () {
+ if (this._open)
+ return;
+ this._open = true;
+ this.actor.show();
+ this.emit('open-state-changed', this._open);
+ },
+
+ close: function () {
+ if (!this._open)
+ return;
+ this._open = false;
+ this.actor.hide();
+ this.emit('open-state-changed', this._open);
+ },
+
+ destroyContent: function() {
+ let children = this.content.get_children();
+ for (let i = 0; i < children.length; i++) {
+ children[i].destroy();
+ }
+ },
+
+ toggle: function () {
+ if (this._open)
+ this.close();
+ else
+ this.open();
+ }
+}
+Signals.addSignalMethods(Pane.prototype);
+
+function ResultArea(displayClass, enableNavigation) {
+ this._init(displayClass, enableNavigation);
+}
+
+ResultArea.prototype = {
+ _init : function(displayClass, enableNavigation) {
+ this.actor = new Big.Box({ orientation: Big.BoxOrientation.VERTICAL });
+ this.resultsContainer = new Big.Box({ orientation: Big.BoxOrientation.HORIZONTAL,
+ spacing: DEFAULT_PADDING
+ });
+ this.actor.append(this.resultsContainer, Big.BoxPackFlags.EXPAND);
+ this.navContainer = new Big.Box({ orientation: Big.BoxOrientation.VERTICAL });
+ this.resultsContainer.append(this.navContainer, Big.BoxPackFlags.NONE);
+
+ this.display = new displayClass();
+
+ this.navArea = this.display.getNavigationArea();
+ if (enableNavigation && this.navArea)
+ this.navContainer.append(this.navArea, Big.BoxPackFlags.EXPAND);
+
+ this.resultsContainer.append(this.display.actor, Big.BoxPackFlags.EXPAND);
+
+ this.controlBox = new Big.Box({ x_align: Big.BoxAlignment.CENTER });
+ this.controlBox.append(this.display.displayControl, Big.BoxPackFlags.NONE);
+ this.actor.append(this.controlBox, Big.BoxPackFlags.EXPAND);
+
+ this.display.load();
+ }
+}
+
+// Utility function shared between ResultPane and the DocDisplay in the main dash.
+// Connects to the detail signal of the display, and on-demand creates a new
+// pane.
+function createPaneForDetails(dash, display, detailsWidth) {
+ let detailPane = null;
+ display.connect('show-details', Lang.bind(this, function(display, index) {
+ if (detailPane == null) {
+ detailPane = new Pane();
+ detailPane.connect('open-state-changed', Lang.bind(this, function (pane, isOpen) {
+ if (!isOpen) {
+ /* Ensure we don't keep around large preview textures */
+ detailPane.destroyContent();
+ }
+ }));
+ dash._addPane(detailPane);
+ }
+
+ if (index >= 0) {
+ detailPane.destroyContent();
+ let details = display.createDetailsForIndex(index, detailsWidth, -1);
+ detailPane.content.append(details, Big.BoxPackFlags.EXPAND);
+ detailPane.open();
+ } else {
+ detailPane.close();
+ }
+ }));
+ return null;
+}
+
+function ResultPane(dash, detailsWidth) {
+ this._init(dash, detailsWidth);
+}
+
+ResultPane.prototype = {
+ __proto__: Pane.prototype,
+
+ _init: function(dash, detailsWidth) {
+ Pane.prototype._init.call(this);
+ this._dash = dash;
+ this._detailsWidth = detailsWidth;
+ },
+
+ // Create an instance of displayClass and pack it into this pane's
+ // content area. Return the displayClass instance.
+ packResults: function(displayClass, enableNavigation) {
+ let resultArea = new ResultArea(displayClass, enableNavigation);
+
+ createPaneForDetails(this._dash, resultArea.display, this._detailsWidth);
+
+ this.content.append(resultArea.actor, Big.BoxPackFlags.EXPAND);
+ this.connect('open-state-changed', Lang.bind(this, function(pane, isOpen) {
+ resultArea.display.resetState();
+ }));
+ return resultArea.display;
+ }
+}
+
+function SearchEntry() {
+ this._init();
+}
+
+SearchEntry.prototype = {
+ _init : function() {
+ this.actor = new Big.Box({ orientation: Big.BoxOrientation.HORIZONTAL,
+ y_align: Big.BoxAlignment.CENTER,
+ background_color: DASH_SEARCH_BG_COLOR,
+ corner_radius: 4,
+ spacing: DEFAULT_PADDING,
+ padding: DEFAULT_PADDING
+ });
+
+ let icon = new Gio.ThemedIcon({ name: 'gtk-find' });
+ let searchIconTexture = Shell.TextureCache.get_default().load_gicon(icon, 16);
+ this.actor.append(searchIconTexture, Big.BoxPackFlags.NONE);
+
+ this.pane = null;
+
+ // We need to initialize the text for the entry to have the cursor displayed
+ // in it. See http://bugzilla.openedhand.com/show_bug.cgi?id=1365
+ this.entry = new Clutter.Text({ font_name: "Sans 14px",
+ editable: true,
+ activatable: true,
+ singleLineMode: true,
+ text: ""
+ });
+ this.actor.append(this.entry, Big.BoxPackFlags.EXPAND);
+ },
+
+ setPane: function (pane) {
+ this._pane = pane;
+ }
+};
+Signals.addSignalMethods(SearchEntry.prototype);
+
+function MoreLink() {
+ this._init();
+}
+
+MoreLink.prototype = {
+ _init : function () {
+ this.actor = new Big.Box({ orientation: Big.BoxOrientation.HORIZONTAL,
+ padding_left: DEFAULT_PADDING,
+ padding_right: DEFAULT_PADDING });
+ let global = Shell.Global.get();
+ let inactiveUri = "file://" + global.imagedir + "view-more.svg";
+ let activeUri = "file://" + global.imagedir + "view-more-activated.svg";
+ this._inactiveIcon = Shell.TextureCache.get_default().load_uri_sync(Shell.TextureCachePolicy.FOREVER,
+ inactiveUri, 29, 18);
+ this._activeIcon = Shell.TextureCache.get_default().load_uri_sync(Shell.TextureCachePolicy.FOREVER,
+ activeUri, 29, 18);
+ this._iconBox = new Big.Box({ reactive: true });
+ this._iconBox.append(this._inactiveIcon, Big.BoxPackFlags.NONE);
+ this.actor.append(this._iconBox, Big.BoxPackFlags.END);
+
+ this.pane = null;
+
+ this._iconBox.connect('button-press-event', Lang.bind(this, function (b, e) {
+ if (this.pane == null) {
+ // Ensure the pane is created; the activated handler will call setPane
+ this.emit('activated');
+ }
+ this._pane.toggle();
+ return true;
+ }));
+ },
+
+ setPane: function (pane) {
+ this._pane = pane;
+ this._pane.connect('open-state-changed', Lang.bind(this, function(pane, isOpen) {
+ this._iconBox.remove_all();
+ this._iconBox.append(isOpen ? this._activeIcon : this._inactiveIcon,
+ Big.BoxPackFlags.NONE);
+ }));
+ }
+}
+
+Signals.addSignalMethods(MoreLink.prototype);
+
+function SectionHeader(title) {
+ this._init(title);
+}
+
+SectionHeader.prototype = {
+ _init : function (title) {
+ this.actor = new Big.Box({ orientation: Big.BoxOrientation.HORIZONTAL });
+ let text = new Clutter.Text({ color: DASH_SECTION_COLOR,
+ font_name: "Sans Bold 10px",
+ text: title });
+ this.moreLink = new MoreLink();
+ this.actor.append(text, Big.BoxPackFlags.EXPAND);
+ this.actor.append(this.moreLink.actor, Big.BoxPackFlags.END);
+ }
+}
+
+function Dash(displayGridColumnWidth) {
+ this._init(displayGridColumnWidth);
+}
+
+Dash.prototype = {
+ _init : function(displayGridColumnWidth) {
+ this._width = displayGridColumnWidth;
+
+ this._detailsWidth = displayGridColumnWidth * 2;
+
+ let global = Shell.Global.get();
+
+ // dash and the popup panes need to be reactive so that the clicks in unoccupied places on them
+ // are not passed to the transparent background underneath them. This background is used for the workspaces area when
+ // the additional dash panes are being shown and it handles clicks by closing the additional panes, so that the user
+ // can interact with the workspaces. However, this behavior is not desirable when the click is actually over a pane.
+ //
+ // We have to make the individual panes reactive instead of making the whole dash actor reactive because the width
+ // of the Group actor ends up including the width of its hidden children, so we were getting a reactive object as
+ // wide as the details pane that was blocking the clicks to the workspaces underneath it even when the details pane
+ // was actually hidden.
+ this.actor = new Big.Box({ orientation: Big.BoxOrientation.HORIZONTAL,
+ width: this._width,
+ padding: DEFAULT_PADDING,
+ reactive: true });
+
+ this.dashContainer = new Big.Box({ orientation: Big.BoxOrientation.VERTICAL,
+ spacing: DASH_SECTION_SPACING });
+ this.actor.append(this.dashContainer, Big.BoxPackFlags.EXPAND);
+
+ // The currently active popup display
+ this._activePane = null;
+
+ /***** Search *****/
+
+ this._searchPane = null;
+ this._searchActive = false;
+ this._searchEntry = new SearchEntry();
+ this.dashContainer.append(this._searchEntry.actor, Big.BoxPackFlags.NONE);
+
+ this._searchAreaApps = null;
+ this._searchAreaDocs = null;
+
+ this._searchQueued = false;
+ this._searchEntry.entry.connect('text-changed', Lang.bind(this, function (se, prop) {
+ this._searchActive = this._searchEntry.text != '';
+ if (this._searchQueued)
+ return;
+ if (this._searchPane == null) {
+ this._searchPane = new ResultPane(this, this._detailsWidth);
+ this._searchPane.content.append(new Clutter.Text({ color: DASH_SECTION_COLOR,
+ font_name: 'Sans Bold 10px',
+ text: "APPLICATIONS" }),
+ Big.BoxPackFlags.NONE);
+ this._searchAreaApps = this._searchPane.packResults(AppDisplay.AppDisplay, false);
+ this._searchPane.content.append(new Clutter.Text({ color: DASH_SECTION_COLOR,
+ font_name: 'Sans Bold 10px',
+ text: "RECENT DOCUMENTS" }),
+ Big.BoxPackFlags.NONE);
+ this._searchAreaDocs = this._searchPane.packResults(DocDisplay.DocDisplay, false);
+ this._addPane(this._searchPane);
+ this._searchEntry.setPane(this._searchPane);
+ }
+ this._searchQueued = true;
+ Mainloop.timeout_add(250, Lang.bind(this, function() {
+ // Strip leading and trailing whitespace
+ let text = this._searchEntry.entry.text.replace(/^\s+/g, "").replace(/\s+$/g, "");
+ this._searchQueued = false;
+ this._searchAreaApps.setSearch(text);
+ this._searchAreaDocs.setSearch(text);
+ if (text == '')
+ this._searchPane.close();
+ else
+ this._searchPane.open();
+ return false;
+ }));
+ }));
+ this._searchEntry.entry.connect('activate', Lang.bind(this, function (se) {
+ // only one of the displays will have an item selected, so it's ok to
+ // call activateSelected() on all of them
+ this._searchAreaApps.activateSelected();
+ this._searchAreaDocs.activateSelected();
+ return true;
+ }));
+ this._searchEntry.entry.connect('key-press-event', Lang.bind(this, function (se, e) {
+ let symbol = Shell.get_event_key_symbol(e);
+ if (symbol == Clutter.Escape) {
+ // Escape will keep clearing things back to the desktop. First, if
+ // we have active text, we remove it.
+ if (this._searchEntry.entry.text != '')
+ this._searchEntry.entry.text = '';
+ // Next, if we're in one of the "more" modes or showing the details pane, close them
+ else if (this._activePane != null)
+ this._activePane.close();
+ // Finally, just close the overlay entirely
+ else
+ Main.overlay.hide();
+ return true;
+ } else if (symbol == Clutter.Up) {
+ if (!this._searchActive)
+ return true;
+ // selectUp and selectDown wrap around in their respective displays
+ // too, but there doesn't seem to be any flickering if we first select
+ // something in one display, but then unset the selection, and move
+ // it to the other display, so it's ok to do that.
+ if (this._searchAreaDocs.hasSelected())
+ this._searchAreaDocs.selectUp();
+ else if (this._searchAreaApps.hasItems())
+ this._searchAreaApps.selectUp();
+ else
+ this._searchAreaDocs.selectUp();
+ return true;
+ } else if (symbol == Clutter.Down) {
+ if (!this._searchActive)
+ return true;
+ if (this._searchAreaDocs.hasSelected())
+ this._searchAreaDocs.selectDown();
+ else if (this._searchAreaApps.hasItems())
+ this._searchAreaApps.selectDown();
+ else
+ this._searchAreaDocs.selectDown();
+ return true;
+ }
+ return false;
+ }));
+
+ /***** Applications *****/
+
+ let appsHeader = new SectionHeader("APPLICATIONS");
+ this._appsSection = new Big.Box({ spacing: DEFAULT_PADDING });
+ this._appsSection.append(appsHeader.actor, Big.BoxPackFlags.NONE);
+
+ this._appsContent = new Big.Box({ orientation: Big.BoxOrientation.HORIZONTAL });
+ this._appsSection.append(this._appsContent, Big.BoxPackFlags.EXPAND);
+ this._appWell = new AppDisplay.AppWell(this._width);
+ this._appsContent.append(this._appWell.actor, Big.BoxPackFlags.EXPAND);
+
+ this._moreAppsPane = null;
+ appsHeader.moreLink.connect('activated', Lang.bind(this, function (link) {
+ if (this._moreAppsPane == null) {
+ this._moreAppsPane = new ResultPane(this, this._detailsWidth);
+ this._moreAppsPane.packResults(AppDisplay.AppDisplay, true);
+ this._addPane(this._moreAppsPane);
+ link.setPane(this._moreAppsPane);
+ }
+ }));
+
+ this.dashContainer.append(this._appsSection, Big.BoxPackFlags.NONE);
+
+ /***** Documents *****/
+
+ this._docsSection = new Big.Box({ orientation: Big.BoxOrientation.VERTICAL,
+ spacing: DEFAULT_PADDING });
+ this._moreDocsPane = null;
+
+ let docsHeader = new SectionHeader("RECENT DOCUMENTS");
+ this._docsSection.append(docsHeader.actor, Big.BoxPackFlags.NONE);
+
+ this._docDisplay = new DocDisplay.DocDisplay();
+ this._docDisplay.load();
+ this._docsSection.append(this._docDisplay.actor, Big.BoxPackFlags.EXPAND);
+
+ createPaneForDetails(this, this._docDisplay, this._detailsWidth);
+
+ docsHeader.moreLink.connect('activated', Lang.bind(this, function (link) {
+ if (this._moreDocsPane == null) {
+ this._moreDocsPane = new ResultPane(this, this._detailsWidth);
+ this._moreDocsPane.packResults(DocDisplay.DocDisplay, true);
+ this._addPane(this._moreDocsPane);
+ link.setPane(this._moreDocsPane);
+ }
+ }));
+
+ this.dashContainer.append(this._docsSection, Big.BoxPackFlags.EXPAND);
+ },
+
+ show: function() {
+ let global = Shell.Global.get();
+ global.stage.set_key_focus(this._searchEntry.entry);
+ },
+
+ hide: function() {
+ this._firstSelectAfterOverlayShow = true;
+ if (this._searchEntry.entry.text != '')
+ this._searchEntry.entry.text = '';
+ if (this._activePane != null)
+ this._activePane.close();
+ },
+
+ closePanes: function () {
+ if (this._activePane != null)
+ this._activePane.close();
+ },
+
+ _addPane: function(pane) {
+ pane.connect('open-state-changed', Lang.bind(this, function (pane, isOpen) {
+ if (isOpen) {
+ if (pane != this._activePane && this._activePane != null) {
+ this._activePane.close();
+ }
+ this._activePane = pane;
+ } else if (pane == this._activePane) {
+ this._activePane = null;
+ }
+ }));
+ Main.overlay.addPane(pane);
+ }
+};
+Signals.addSignalMethods(Dash.prototype);
*
* docInfo - DocInfo object containing information about the document
* currentSeconds - current number of seconds since the epoch
- * availableWidth - total width available for the item
*/
-function DocDisplayItem(docInfo, currentSecs, availableWidth) {
- this._init(docInfo, currentSecs, availableWidth);
+function DocDisplayItem(docInfo, currentSecs) {
+ this._init(docInfo, currentSecs);
}
DocDisplayItem.prototype = {
__proto__: GenericDisplay.GenericDisplayItem.prototype,
- _init : function(docInfo, currentSecs, availableWidth) {
- GenericDisplay.GenericDisplayItem.prototype._init.call(this, availableWidth);
+ _init : function(docInfo, currentSecs) {
+ GenericDisplay.GenericDisplayItem.prototype._init.call(this);
this._docInfo = docInfo;
-
+
this._setItemInfo(docInfo.name, "");
this._timeoutTime = -1;
/* This class represents a display containing a collection of document items.
* The documents are sorted by how recently they were last visited.
- *
- * width - width available for the display
*/
-function DocDisplay(width) {
- this._init(width);
+function DocDisplay() {
+ this._init();
}
DocDisplay.prototype = {
__proto__: GenericDisplay.GenericDisplay.prototype,
- _init : function(width) {
- GenericDisplay.GenericDisplay.prototype._init.call(this, width);
+ _init : function() {
+ GenericDisplay.GenericDisplay.prototype._init.call(this);
let me = this;
// We keep a single timeout callback for updating last visited times
},
// Creates a DocDisplayItem based on itemInfo, which is expected to be a DocInfo object.
- _createDisplayItem: function(itemInfo, width) {
+ _createDisplayItem: function(itemInfo) {
let currentSecs = new Date().getTime() / 1000;
- let docDisplayItem = new DocDisplayItem(itemInfo, currentSecs, width);
+ let docDisplayItem = new DocDisplayItem(itemInfo, currentSecs);
this._updateTimeoutCallback(docDisplayItem, currentSecs);
return docDisplayItem;
},
const Button = imports.ui.button;
const DND = imports.ui.dnd;
const Link = imports.ui.link;
+const Main = imports.ui.main;
const ITEM_DISPLAY_NAME_COLOR = new Clutter.Color();
ITEM_DISPLAY_NAME_COLOR.from_pixel(0xffffffff);
DISPLAY_CONTROL_SELECTED_COLOR.from_pixel(0x112288ff);
const PREVIEW_BOX_BACKGROUND_COLOR = new Clutter.Color();
PREVIEW_BOX_BACKGROUND_COLOR.from_pixel(0xADADADf0);
-const HOT_PINK_DEBUG = new Clutter.Color();
-HOT_PINK_DEBUG.from_pixel(0xFF8888FF);
+
+const DEFAULT_PADDING = 4;
const ITEM_DISPLAY_HEIGHT = 50;
const ITEM_DISPLAY_ICON_SIZE = 48;
-const ITEM_DISPLAY_PADDING_TOP = 1;
+const ITEM_DISPLAY_PADDING = 1;
const ITEM_DISPLAY_PADDING_RIGHT = 2;
const DEFAULT_COLUMN_GAP = 6;
-const LABEL_HEIGHT = 16;
const PREVIEW_ICON_SIZE = 96;
const PREVIEW_BOX_PADDING = 6;
-const PREVIEW_BOX_SPACING = 4;
-const PREVIEW_BOX_CORNER_RADIUS = 10;
+const PREVIEW_BOX_SPACING = DEFAULT_PADDING;
+const PREVIEW_BOX_CORNER_RADIUS = 10;
// how far relative to the full item width the preview box should be placed
const PREVIEW_PLACING = 3/4;
const PREVIEW_DETAILS_MIN_WIDTH = PREVIEW_ICON_SIZE * 2;
const INFORMATION_BUTTON_SIZE = 16;
/* This is a virtual class that represents a single display item containing
- * a name, a description, and an icon. It allows selecting an item and represents
+ * a name, a description, and an icon. It allows selecting an item and represents
* it by highlighting it with a different background color than the default.
- *
- * availableWidth - total width available for the item
*/
-function GenericDisplayItem(availableWidth) {
- this._init(availableWidth);
+function GenericDisplayItem() {
+ this._init();
}
GenericDisplayItem.prototype = {
- _init: function(availableWidth) {
- this._availableWidth = availableWidth;
+ _init: function() {
+ this.actor = new Big.Box({ orientation: Big.BoxOrientation.HORIZONTAL,
+ spacing: ITEM_DISPLAY_PADDING,
+ reactive: true,
+ background_color: ITEM_DISPLAY_BACKGROUND_COLOR,
+ corner_radius: 4,
+ height: ITEM_DISPLAY_HEIGHT });
- this.actor = new Clutter.Group({ reactive: true,
- width: availableWidth,
- height: ITEM_DISPLAY_HEIGHT });
this.actor._delegate = this;
- this.actor.connect('button-release-event',
- Lang.bind(this,
+ this.actor.connect('button-release-event',
+ Lang.bind(this,
function() {
// Activates the item by launching it
this.emit('activate');
let draggable = DND.makeDraggable(this.actor);
draggable.connect('drag-begin', Lang.bind(this, this._onDragBegin));
- this._bg = new Big.Box({ background_color: ITEM_DISPLAY_BACKGROUND_COLOR,
- corner_radius: 4,
- x: 0, y: 0,
- width: availableWidth, height: ITEM_DISPLAY_HEIGHT });
- this.actor.add_actor(this._bg);
+ this._infoContent = new Big.Box({ orientation: Big.BoxOrientation.HORIZONTAL,
+ spacing: DEFAULT_PADDING });
+ this.actor.append(this._infoContent, Big.BoxPackFlags.EXPAND);
+
+ this._iconBox = new Big.Box();
+ this._infoContent.append(this._iconBox, Big.BoxPackFlags.NONE);
+
+ this._infoText = new Big.Box({ orientation: Big.BoxOrientation.VERTICAL,
+ spacing: DEFAULT_PADDING });
+ this._infoContent.append(this._infoText, Big.BoxPackFlags.EXPAND);
let global = Shell.Global.get();
let infoIconUri = "file://" + global.imagedir + "info.svg";
INFORMATION_BUTTON_SIZE,
INFORMATION_BUTTON_SIZE);
this._informationButton = new Button.iconButton(this.actor, INFORMATION_BUTTON_SIZE, infoIcon);
- this._informationButton.actor.x = availableWidth - ITEM_DISPLAY_PADDING_RIGHT - INFORMATION_BUTTON_SIZE;
- this._informationButton.actor.y = ITEM_DISPLAY_HEIGHT / 2 - INFORMATION_BUTTON_SIZE / 2;
-
- // Connecting to the button-press-event for the information button ensures that the actor,
+ let buttonBox = new Big.Box({ width: INFORMATION_BUTTON_SIZE + 2 * DEFAULT_PADDING,
+ height: INFORMATION_BUTTON_SIZE,
+ padding_left: DEFAULT_PADDING, padding_right: DEFAULT_PADDING,
+ y_align: Big.BoxAlignment.CENTER });
+ buttonBox.append(this._informationButton.actor, Big.BoxPackFlags.NONE);
+ this.actor.append(buttonBox, Big.BoxPackFlags.END);
+
+ // Connecting to the button-press-event for the information button ensures that the actor,
// which is a draggable actor, does not get the button-press-event and doesn't initiate
// the dragging, which then prevents us from getting the button-release-event for the button.
this._informationButton.actor.connect('button-press-event',
Lang.bind(this,
function() {
// Selects the item by highlighting it and displaying its details
- this.emit('select');
+ this.emit('show-details');
return true;
}));
- this.actor.add_actor(this._informationButton.actor);
- this._informationButton.actor.lower_bottom();
this._name = null;
this._description = null;
color = ITEM_DISPLAY_BACKGROUND_COLOR;
this._informationButton.forceShow(false);
}
- this._bg.background_color = color;
+ this.actor.background_color = color;
},
/*
- * Returns an actor containing item details. In the future details can have more information than what
+ * Returns an actor containing item details. In the future details can have more information than what
* the preview pop-up has and be item-type specific.
*
* availableWidth - width available for displaying details
- * availableHeight - height available for displaying details
- */
- createDetailsActor: function(availableWidth, availableHeight) {
-
+ */
+ createDetailsActor: function(availableWidth) {
+
let details = new Big.Box({ orientation: Big.BoxOrientation.VERTICAL,
spacing: PREVIEW_BOX_SPACING,
width: availableWidth });
let detailsName = new Clutter.Text({ color: ITEM_DISPLAY_NAME_COLOR,
font_name: "Sans bold 14px",
line_wrap: true,
- text: this._name.text});
+ text: this._name.text });
textDetails.append(detailsName, Big.BoxPackFlags.NONE);
let detailsDescription = new Clutter.Text({ color: ITEM_DISPLAY_NAME_COLOR,
mainDetails.append(textDetails, Big.BoxPackFlags.EXPAND);
let previewIcon = this._createPreviewIcon();
- let largePreviewIcon = this._createLargePreviewIcon(availableWidth, Math.max(0, availableHeight - mainDetails.height - PREVIEW_BOX_SPACING));
+ let largePreviewIcon = this._createLargePreviewIcon(availableWidth, -1);
if (previewIcon != null && largePreviewIcon == null) {
mainDetails.prepend(previewIcon, Big.BoxPackFlags.NONE);
}
this._icon = this._createIcon();
- this.actor.add_actor(this._icon);
+ this._iconBox.append(this._icon, Big.BoxPackFlags.NONE);
- let textWidth = this._availableWidth - (ITEM_DISPLAY_ICON_SIZE + 4) - INFORMATION_BUTTON_SIZE - ITEM_DISPLAY_PADDING_RIGHT;
this._name = new Clutter.Text({ color: ITEM_DISPLAY_NAME_COLOR,
font_name: "Sans 14px",
- width: textWidth,
ellipsize: Pango.EllipsizeMode.END,
- text: nameText,
- x: ITEM_DISPLAY_ICON_SIZE + 4,
- y: ITEM_DISPLAY_PADDING_TOP });
- this.actor.add_actor(this._name);
+ text: nameText });
+ this._infoText.append(this._name, Big.BoxPackFlags.EXPAND);
+
this._description = new Clutter.Text({ color: ITEM_DISPLAY_DESCRIPTION_COLOR,
font_name: "Sans 12px",
- width: textWidth,
ellipsize: Pango.EllipsizeMode.END,
- text: descriptionText ? descriptionText : "",
- x: this._name.x,
- y: this._name.height + 4 });
- this.actor.add_actor(this._description);
+ text: descriptionText ? descriptionText : ""
+ });
+ this._infoText.append(this._description, Big.BoxPackFlags.EXPAND);
},
// Sets the description text for the item, including the description text
- // in the details actors that have been created for the item.
+ // in the details actors that have been created for the item.
_setDescriptionText: function(text) {
this._description.text = text;
for (let i = 0; i < this._detailsDescriptions.length; i++) {
/* This is a virtual class that represents a display containing a collection of items
* that can be filtered with a search string.
- *
- * width - width available for the display
*/
-function GenericDisplay(width) {
- this._init(width);
+function GenericDisplay() {
+ this._init();
}
GenericDisplay.prototype = {
- _init : function(width) {
+ _init : function() {
this._search = '';
this._expanded = false;
- this._width = width;
this._maxItemsPerPage = null;
- this._list = new Shell.OverflowList({ width: this._width,
- spacing: 6.0,
+ this._list = new Shell.OverflowList({ spacing: 6.0,
item_height: ITEM_DISPLAY_HEIGHT });
this._list.connect('notify::n-pages', Lang.bind(this, function (grid, alloc) {
this._matchedItems = [];
// map<itemId, GenericDisplayItem>
this._displayedItems = {};
+ this._openDetailIndex = -1;
this._selectedIndex = -1;
// These two are public - .actor is the normal "actor subclass" property,
// but we also expose a .displayControl actor which is separate.
this.displayControl = new Big.Box({ background_color: ITEM_DISPLAY_BACKGROUND_COLOR,
spacing: 12,
orientation: Big.BoxOrientation.HORIZONTAL});
-
- this._availableWidthForItemDetails = width;
- this.selectedItemDetails = new Big.Box({});
},
//// Public methods ////
- // Sets dimensions available for the item details display.
- setAvailableDimensionsForItemDetails: function(availableWidth, availableHeight) {
- this._availableWidthForItemDetails = availableWidth;
- this._availableHeightForItemDetails = availableHeight;
- },
-
- // Returns dimensions available for the item details display.
- getAvailableDimensionsForItemDetails: function() {
- return [this._availableWidthForItemDetails, this._availableHeightForItemDetails];
- },
-
// Sets the search string and displays the matching items.
setSearch: function(text) {
this._search = text.toLowerCase();
activateSelected: function() {
if (this._selectedIndex != -1) {
let selected = this._findDisplayedByIndex(this._selectedIndex);
- selected.launch()
- this.emit('activated');
+ selected.launch();
+ this.unsetSelected();
+ Main.overlay.hide();
}
},
return this._list.displayedCount > 0;
},
- // Updates the displayed items and makes the display actor visible.
- show: function() {
- this._list.show();
+ // Load the initial state
+ load: function() {
this._redisplay(true);
},
- // Hides the display actor.
- hide: function() {
- this._list.hide();
+ // Should be called when the display is closed
+ resetState: function() {
this._filterReset();
- this._removeAllDisplayItems();
+ this._openDetailIndex = -1;
},
// Returns an actor which acts as a sidebar; this is used for
return null;
},
+ createDetailsForIndex: function(index, width, height) {
+ let item = this._findDisplayedByIndex(index);
+ return item.createDetailsActor(width, height);
+ },
+
//// Protected methods ////
/*
}
let itemInfo = this._allItems[itemId];
- let displayItem = this._createDisplayItem(itemInfo, this._width);
+ let displayItem = this._createDisplayItem(itemInfo);
displayItem.connect('activate',
Lang.bind(this,
this.activateSelected();
}));
- displayItem.connect('select',
+ displayItem.connect('show-details',
Lang.bind(this,
function() {
- // update the selection
- this._selectIndex(this._getIndexOfDisplayedActor(displayItem.actor));
+ let index = this._getIndexOfDisplayedActor(displayItem.actor);
+ /* Close the details pane if already open */
+ if (index == this._openDetailIndex) {
+ this._openDetailIndex = -1;
+ this.emit('show-details', -1);
+ } else {
+ this._openDetailIndex = index;
+ this.emit('show-details', index);
+ }
}));
this._list.add_actor(displayItem.actor);
this._displayedItems[itemId] = displayItem;
let displayItem = this._displayedItems[itemId];
let displayItemIndex = this._getIndexOfDisplayedActor(displayItem.actor);
- if (this.hasSelected() && (count == 1 || !this._list.visible)) {
+ if (this.hasSelected() && count == 1) {
this.unsetSelected();
} else if (this.hasSelected() && displayItemIndex < this._selectedIndex) {
this.selectUp();
- }
+ }
displayItem.destroy();
* their own while the user was browsing through the result pages.
*/
_redisplay: function(resetPage) {
- if (!this._list.visible)
- return;
-
this._refreshCache();
if (!this._filterActive())
this._setDefaultList();
let pageControl = new Link.Link({ color: (i == pageNumber) ? DISPLAY_CONTROL_SELECTED_COLOR : ITEM_DISPLAY_DESCRIPTION_COLOR,
font_name: "Sans Bold 16px",
text: (i+1) + "",
- height: LABEL_HEIGHT,
reactive: (i == pageNumber) ? false : true});
this.displayControl.append(pageControl.actor, Big.BoxPackFlags.NONE);
// If the item is already selected, all we do is toggling the details pane.
if (this._selectedIndex == index && index >= 0) {
- this.emit('toggle-details');
+ this.emit('details', index);
return;
}
// Cleanup from the previous item
if (this._selectedIndex >= 0) {
this._findDisplayedByIndex(this._selectedIndex).markSelected(false);
-
- // Calling destroy() gets large image previews released as quickly as
- // possible, if we just removed them, they might hang around for a while
- // until the actor was garbage collected.
- let children = this.selectedItemDetails.get_children();
- for (let i = 0; i < children.length; i++)
- children[i].destroy();
-
- this.selectedItemDetails.remove_all();
}
this._selectedIndex = index;
// Mark the new item as selected and create its details pane
let item = this._findDisplayedByIndex(index);
item.markSelected(true);
- this.selectedItemDetails.append(item.createDetailsActor(this._availableWidthForItemDetails,
- this._availableHeightForItemDetails), Big.BoxPackFlags.NONE);
this.emit('selected');
}
};
const Link = imports.ui.link;
const Main = imports.ui.main;
const Panel = imports.ui.panel;
+const Dash = imports.ui.dash;
const Tweener = imports.ui.tweener;
const Workspaces = imports.ui.workspaces;
// than 3/2, because the rule of thirds is used for positioning (see below).
const BACKGROUND_SCALE = 2;
-const LABEL_HEIGHT = 16;
-const DASH_MIN_WIDTH = 250;
-const DASH_OUTER_PADDING = 4;
-const DASH_SECTION_PADDING = 6;
-const DASH_SECTION_SPACING = 6;
-const DASH_CORNER_RADIUS = 5;
-// This is the height of section components other than the item display.
-const DASH_SECTION_MISC_HEIGHT = (LABEL_HEIGHT + DASH_SECTION_SPACING) * 2 + DASH_SECTION_PADDING;
-const DASH_SEARCH_BG_COLOR = new Clutter.Color();
-DASH_SEARCH_BG_COLOR.from_pixel(0xffffffff);
-const DASH_TEXT_COLOR = new Clutter.Color();
-DASH_TEXT_COLOR.from_pixel(0xffffffff);
-
// Time for initial animation going into overlay mode
const ANIMATION_TIME = 0.25;
const COLUMNS_WIDE_SCREEN = 5;
const ROWS_WIDE_SCREEN = 10;
+const DEFAULT_PADDING = 4;
+
// Padding around workspace grid / Spacing between Dash and Workspaces
const WORKSPACE_GRID_PADDING = 12;
const STATE_PENDING_INACTIVE = false;
const STATE_INACTIVE = false;
-// The dash has a slightly transparent blue background with a gradient.
-const DASH_LEFT_COLOR = new Clutter.Color();
-DASH_LEFT_COLOR.from_pixel(0x0d131fbb);
-const DASH_MIDDLE_COLOR = new Clutter.Color();
-DASH_MIDDLE_COLOR.from_pixel(0x0d131faa);
-const DASH_RIGHT_COLOR = new Clutter.Color();
-DASH_RIGHT_COLOR.from_pixel(0x0d131fcc);
-
-const DASH_BORDER_COLOR = new Clutter.Color();
-DASH_BORDER_COLOR.from_pixel(0x213b5dfa);
-
-const DASH_BORDER_WIDTH = 2;
-
-// The results and details panes have a somewhat transparent blue background with a gradient.
-const PANE_LEFT_COLOR = new Clutter.Color();
-PANE_LEFT_COLOR.from_pixel(0x0d131ff4);
-const PANE_MIDDLE_COLOR = new Clutter.Color();
-PANE_MIDDLE_COLOR.from_pixel(0x0d131ffa);
-const PANE_RIGHT_COLOR = new Clutter.Color();
-PANE_RIGHT_COLOR.from_pixel(0x0d131ff4);
-
const SHADOW_COLOR = new Clutter.Color();
SHADOW_COLOR.from_pixel(0x00000033);
const TRANSPARENT_COLOR = new Clutter.Color();
let displayGridColumnWidth = null;
let displayGridRowHeight = null;
-function SearchEntry(width) {
- this._init(width);
-}
-
-SearchEntry.prototype = {
- _init : function() {
- this.actor = new Big.Box({ orientation: Big.BoxOrientation.HORIZONTAL,
- y_align: Big.BoxAlignment.CENTER,
- background_color: DASH_SEARCH_BG_COLOR,
- corner_radius: 4,
- spacing: 4,
- padding_left: 4,
- padding_right: 4,
- height: 24
- });
-
- let icontheme = Gtk.IconTheme.get_default();
- let searchIconTexture = new Clutter.Texture({});
- let searchIconPath = icontheme.lookup_icon('gtk-find', 16, 0).get_filename();
- searchIconTexture.set_from_file(searchIconPath);
- this.actor.append(searchIconTexture, 0);
-
- // We need to initialize the text for the entry to have the cursor displayed
- // in it. See http://bugzilla.openedhand.com/show_bug.cgi?id=1365
- this.entry = new Clutter.Text({ font_name: "Sans 14px",
- editable: true,
- activatable: true,
- singleLineMode: true,
- text: ""
- });
- this.entry.connect('text-changed', Lang.bind(this, function (e) {
- let text = this.entry.text;
- }));
- this.actor.append(this.entry, Big.BoxPackFlags.EXPAND);
- }
-};
-
-function ItemResults(resultsWidth, resultsHeight, displayClass, labelText) {
- this._init(resultsWidth, resultsHeight, displayClass, labelText);
-}
-
-ItemResults.prototype = {
- _init: function(resultsWidth, resultsHeight, displayClass, labelText) {
- this._resultsWidth = resultsWidth;
- this._resultsHeight = resultsHeight;
-
- this.actor = new Big.Box({ orientation: Big.BoxOrientation.VERTICAL,
- height: resultsHeight,
- padding: DASH_SECTION_PADDING + DASH_BORDER_WIDTH,
- spacing: DASH_SECTION_SPACING });
-
- this._resultsText = new Clutter.Text({ color: DASH_TEXT_COLOR,
- font_name: "Sans Bold 14px",
- text: labelText });
-
- this.resultsContainer = new Big.Box({ orientation: Big.BoxOrientation.HORIZONTAL,
- spacing: 4 });
- this.navContainer = new Big.Box({ orientation: Big.BoxOrientation.VERTICAL });
- this.resultsContainer.append(this.navContainer, Big.BoxPackFlags.NONE);
-
- // LABEL_HEIGHT is the height of this._resultsText and GenericDisplay.LABEL_HEIGHT is the height
- // of the display controls.
- this._displayHeight = resultsHeight - LABEL_HEIGHT - GenericDisplay.LABEL_HEIGHT - DASH_SECTION_SPACING * 2;
- this.display = new displayClass(resultsWidth);
-
- this.navArea = this.display.getNavigationArea();
- if (this.navArea)
- this.navContainer.append(this.navArea, Big.BoxPackFlags.EXPAND);
-
- this.resultsContainer.append(this.display.actor, Big.BoxPackFlags.EXPAND);
-
- this.controlBox = new Big.Box({ x_align: Big.BoxAlignment.CENTER });
- this.controlBox.append(this.display.displayControl, Big.BoxPackFlags.NONE);
-
- this._unsetSearchMode();
- },
-
- _setSearchMode: function() {
- if (this.navArea)
- this.navArea.hide();
- this.actor.height = this._resultsHeight / NUMBER_OF_SECTIONS_IN_SEARCH;
- let displayHeight = this._displayHeight - this._resultsHeight * (NUMBER_OF_SECTIONS_IN_SEARCH - 1) / NUMBER_OF_SECTIONS_IN_SEARCH;
- this.actor.remove_all();
- this.actor.append(this._resultsText, Big.BoxPackFlags.NONE);
- this.actor.append(this.resultsContainer, Big.BoxPackFlags.EXPAND);
- this.actor.append(this.controlBox, Big.BoxPackFlags.END);
- },
-
- _unsetSearchMode: function() {
- if (this.navArea)
- this.navArea.show();
- this.actor.height = this._resultsHeight;
- this.actor.remove_all();
- this.actor.append(this._resultsText, Big.BoxPackFlags.NONE);
- this.actor.append(this.resultsContainer, Big.BoxPackFlags.EXPAND);
- this.actor.append(this.controlBox, Big.BoxPackFlags.END);
- }
-}
-
-function Dash() {
+function Overlay() {
this._init();
}
-Dash.prototype = {
+Overlay.prototype = {
_init : function() {
let me = this;
- this._moreAppsMode = false;
- this._moreDocsMode = false;
-
- this._width = displayGridColumnWidth;
-
- this._displayWidth = displayGridColumnWidth - DASH_SECTION_PADDING * 2;
- this._resultsWidth = displayGridColumnWidth;
- this._detailsWidth = displayGridColumnWidth * 2;
-
- let global = Shell.Global.get();
-
- let dashHeight = global.screen_height - Panel.PANEL_HEIGHT;
- let resultsHeight = global.screen_height - Panel.PANEL_HEIGHT;
- let detailsHeight = global.screen_height - Panel.PANEL_HEIGHT;
-
- // Size the actor to 0x0 so as not to interfere with DND
- this.actor = new Clutter.Group({ width: 0, height: 0 });
- this.actor.height = global.screen_height;
-
-
- // dashPane, as well as results and details panes need to be reactive so that the clicks in unoccupied places on them
- // are not passed to the transparent background underneath them. This background is used for the workspaces area when
- // the additional dash panes are being shown and it handles clicks by closing the additional panes, so that the user
- // can interact with the workspaces. However, this behavior is not desirable when the click is actually over a pane.
- //
- // We have to make the individual panes reactive instead of making the whole dash actor reactive because the width
- // of the Group actor ends up including the width of its hidden children, so we were getting a reactive object as
- // wide as the details pane that was blocking the clicks to the workspaces underneath it even when the details pane
- // was actually hidden.
- let dashPane = new Big.Box({ orientation: Big.BoxOrientation.HORIZONTAL,
- x: 0,
- y: Panel.PANEL_HEIGHT,
- width: this._width + SHADOW_WIDTH,
- height: dashHeight,
- reactive: true});
-
- let dashBackground = new Big.Box({ orientation: Big.BoxOrientation.HORIZONTAL,
- width: this._width,
- height: dashHeight });
-
- dashPane.append(dashBackground, Big.BoxPackFlags.EXPAND);
-
- let dashLeft = Shell.create_horizontal_gradient(DASH_LEFT_COLOR,
- DASH_MIDDLE_COLOR);
- let dashRight = Shell.create_horizontal_gradient(DASH_MIDDLE_COLOR,
- DASH_RIGHT_COLOR);
- let dashShadow = Shell.create_horizontal_gradient(SHADOW_COLOR,
- TRANSPARENT_COLOR);
- dashShadow.set_width(SHADOW_WIDTH);
-
- dashBackground.append(dashLeft, Big.BoxPackFlags.EXPAND);
- dashBackground.append(dashRight, Big.BoxPackFlags.EXPAND);
- dashPane.append(dashShadow, Big.BoxPackFlags.NONE);
-
- this.actor.add_actor(dashPane);
-
- this.dashOuterContainer = new Big.Box({ orientation: Big.BoxOrientation.VERTICAL,
- x: 0,
- y: dashPane.y,
- width: this._width,
- height: dashHeight,
- padding: DASH_OUTER_PADDING
- });
- this.actor.add_actor(this.dashOuterContainer);
- this.dashContainer = new Big.Box({ orientation: Big.BoxOrientation.VERTICAL });
- this.dashOuterContainer.append(this.dashContainer, Big.BoxPackFlags.EXPAND);
-
- this._searchEntry = new SearchEntry();
- this.dashContainer.append(this._searchEntry.actor, Big.BoxPackFlags.NONE);
-
- this._searchQueued = false;
- this._searchEntry.entry.connect('text-changed', function (se, prop) {
- if (me._searchQueued)
- return;
- me._searchQueued = true;
- Mainloop.timeout_add(250, function() {
- // Strip leading and trailing whitespace
- let text = me._searchEntry.entry.text.replace(/^\s+/g, "").replace(/\s+$/g, "");
- me._searchQueued = false;
- me._resultsAppsSection.display.setSearch(text);
- me._resultsDocsSection.display.setSearch(text);
- if (text == '')
- me._unsetSearchMode();
- else
- me._setSearchMode();
-
- return false;
- });
- });
- this._searchEntry.entry.connect('activate', function (se) {
- // only one of the displays will have an item selected, so it's ok to
- // call activateSelected() on all of them
- me._docDisplay.activateSelected();
- me._resultsAppsSection.display.activateSelected();
- me._resultsDocsSection.display.activateSelected();
- return true;
- });
- this._searchEntry.entry.connect('key-press-event', function (se, e) {
- let symbol = Shell.get_event_key_symbol(e);
- if (symbol == Clutter.Escape) {
- // Escape will keep clearing things back to the desktop. First, if
- // we have active text, we remove it.
- if (me._searchEntry.entry.text != '')
- me._searchEntry.entry.text = '';
- // Next, if we're in one of the "more" modes or showing the details pane, close them
- else if (me._resultsShowing())
- me.unsetMoreMode();
- // Finally, just close the overlay entirely
- else
- me.emit('activated');
- return true;
- } else if (symbol == Clutter.Up) {
- if (!me._resultsShowing())
- return true;
- // selectUp and selectDown wrap around in their respective displays
- // too, but there doesn't seem to be any flickering if we first select
- // something in one display, but then unset the selection, and move
- // it to the other display, so it's ok to do that.
- if (me._resultsDocsSection.display.hasSelected())
- me._resultsDocsSection.display.selectUp();
- else if (me._resultsAppsSection.display.hasItems())
- me._resultsAppsSection.display.selectUp();
- else
- me._resultsDocsSection.display.selectUp();
- return true;
- } else if (symbol == Clutter.Down) {
- if (!me._resultsShowing())
- return true;
- if (me._resultsDocsSection.display.hasSelected())
- me._resultsDocsSection.display.selectDown();
- else if (me._resultsAppsSection.display.hasItems())
- me._resultsAppsSection.display.selectDown();
- else
- me._resultsDocsSection.display.selectDown();
- return true;
- }
- return false;
- });
-
- this._appsText = new Clutter.Text({ color: DASH_TEXT_COLOR,
- font_name: "Sans Bold 14px",
- text: "Applications",
- height: LABEL_HEIGHT});
- this._appsSection = new Big.Box({ padding_top: DASH_SECTION_PADDING,
- spacing: DASH_SECTION_SPACING});
- this._appsSection.append(this._appsText, Big.BoxPackFlags.NONE);
-
- this._itemDisplayHeight = global.screen_height - this._appsSection.y - DASH_SECTION_MISC_HEIGHT * 2;
-
- this._appsContent = new Big.Box({ orientation: Big.BoxOrientation.HORIZONTAL });
- this._appsSection.append(this._appsContent, Big.BoxPackFlags.EXPAND);
- this._appWell = new AppDisplay.AppWell(this._displayWidth);
- this._appsContent.append(this._appWell.actor, Big.BoxPackFlags.EXPAND);
-
- let moreAppsBox = new Big.Box({x_align: Big.BoxAlignment.END});
- this._moreAppsLink = new Link.Link({ color: DASH_TEXT_COLOR,
- font_name: "Sans Bold 14px",
- text: "More...",
- height: LABEL_HEIGHT });
- moreAppsBox.append(this._moreAppsLink.actor, Big.BoxPackFlags.EXPAND);
- this._appsSection.append(moreAppsBox, Big.BoxPackFlags.NONE);
-
- this.dashContainer.append(this._appsSection, Big.BoxPackFlags.NONE);
-
- this._appsSectionDefaultHeight = this._appsSection.height;
-
- this._docsSection = new Big.Box({ padding_top: DASH_SECTION_PADDING,
- padding_bottom: DASH_SECTION_PADDING,
- spacing: DASH_SECTION_SPACING});
-
- this._docsText = new Clutter.Text({ color: DASH_TEXT_COLOR,
- font_name: "Sans Bold 14px",
- text: "Recent Documents",
- height: LABEL_HEIGHT});
- this._docsSection.append(this._docsText, Big.BoxPackFlags.NONE);
-
- this._docDisplay = new DocDisplay.DocDisplay(this._displayWidth);
- this._docsSection.append(this._docDisplay.actor, Big.BoxPackFlags.EXPAND);
-
- let moreDocsBox = new Big.Box({x_align: Big.BoxAlignment.END});
- this._moreDocsLink = new Link.Link({ color: DASH_TEXT_COLOR,
- font_name: "Sans Bold 14px",
- text: "More...",
- height: LABEL_HEIGHT });
- moreDocsBox.append(this._moreDocsLink.actor, Big.BoxPackFlags.EXPAND);
- this._docsSection.append(moreDocsBox, Big.BoxPackFlags.NONE);
-
- this.dashContainer.append(this._docsSection, Big.BoxPackFlags.EXPAND);
-
- this._docsSectionDefaultHeight = this._docsSection.height;
-
- // The "More" or search results area
- this._resultsAppsSection = new ItemResults(this._resultsWidth, resultsHeight, AppDisplay.AppDisplay, "Applications");
- this._resultsDocsSection = new ItemResults(this._resultsWidth, resultsHeight, DocDisplay.DocDisplay, "Documents");
-
- this._resultsPane = new Big.Box({ orientation: Big.BoxOrientation.VERTICAL,
- x: this._width,
- y: Panel.PANEL_HEIGHT,
- height: resultsHeight,
- reactive: true });
-
- let resultsBackground = new Big.Box({ orientation: Big.BoxOrientation.HORIZONTAL,
- height: resultsHeight,
- corner_radius: DASH_CORNER_RADIUS,
- border: DASH_BORDER_WIDTH,
- border_color: DASH_BORDER_COLOR });
- this._resultsPane.add_actor(resultsBackground);
-
- let resultsLeft = Shell.create_horizontal_gradient(PANE_LEFT_COLOR,
- PANE_MIDDLE_COLOR);
- let resultsRight = Shell.create_horizontal_gradient(PANE_MIDDLE_COLOR,
- PANE_RIGHT_COLOR);
- let resultsShadow = Shell.create_horizontal_gradient(SHADOW_COLOR,
- TRANSPARENT_COLOR);
- resultsShadow.set_width(SHADOW_WIDTH);
-
- resultsBackground.append(resultsLeft, Big.BoxPackFlags.EXPAND);
- resultsBackground.append(resultsRight, Big.BoxPackFlags.EXPAND);
- this._resultsPane.add_actor(resultsShadow);
- this._resultsPane.connect('notify::allocation', Lang.bind(this, function (b, a) {
- let width = this._resultsPane.width;
- resultsBackground.width = width;
- resultsShadow.width = width;
- }));
-
- this.actor.add_actor(this._resultsPane);
- this._resultsPane.hide();
-
- this._detailsPane = new Big.Box({ orientation: Big.BoxOrientation.HORIZONTAL,
- y: Panel.PANEL_HEIGHT,
- width: this._detailsWidth + SHADOW_WIDTH,
- height: detailsHeight,
- reactive: true });
- this._firstSelectAfterOverlayShow = true;
-
- let detailsBackground = new Big.Box({ orientation: Big.BoxOrientation.HORIZONTAL,
- width: this._detailsWidth,
- height: detailsHeight,
- corner_radius: DASH_CORNER_RADIUS,
- border: DASH_BORDER_WIDTH,
- border_color: DASH_BORDER_COLOR });
-
- this._detailsPane.append(detailsBackground, Big.BoxPackFlags.EXPAND);
-
- let detailsLeft = Shell.create_horizontal_gradient(PANE_LEFT_COLOR,
- PANE_MIDDLE_COLOR);
- let detailsRight = Shell.create_horizontal_gradient(PANE_MIDDLE_COLOR,
- PANE_RIGHT_COLOR);
- let detailsShadow = Shell.create_horizontal_gradient(SHADOW_COLOR,
- TRANSPARENT_COLOR);
- detailsShadow.set_width(SHADOW_WIDTH);
-
- detailsBackground.append(detailsLeft, Big.BoxPackFlags.EXPAND);
- detailsBackground.append(detailsRight, Big.BoxPackFlags.EXPAND);
- this._detailsPane.append(detailsShadow, Big.BoxPackFlags.NONE);
-
- this._detailsContent = new Big.Box({ padding: DASH_SECTION_PADDING + DASH_BORDER_WIDTH });
- this._detailsPane.add_actor(this._detailsContent);
-
- this.actor.add_actor(this._detailsPane);
- this._detailsPane.hide();
-
- let itemDetailsAvailableWidth = this._detailsWidth - DASH_SECTION_PADDING * 2 - DASH_BORDER_WIDTH * 2;
- let itemDetailsAvailableHeight = detailsHeight - DASH_SECTION_PADDING * 2 - DASH_BORDER_WIDTH * 2;
-
- this._docDisplay.setAvailableDimensionsForItemDetails(itemDetailsAvailableWidth, itemDetailsAvailableHeight);
- this._resultsAppsSection.display.setAvailableDimensionsForItemDetails(itemDetailsAvailableWidth, itemDetailsAvailableHeight);
- this._resultsDocsSection.display.setAvailableDimensionsForItemDetails(itemDetailsAvailableWidth, itemDetailsAvailableHeight);
-
- /* Proxy the activated signals */
- this._appWell.connect('activated', function(well) {
- me.emit('activated');
- });
- this._docDisplay.connect('activated', function(docDisplay) {
- me.emit('activated');
- });
- this._resultsAppsSection.display.connect('activated', function(resultsAppsDisplay) {
- me.emit('activated');
- });
- this._resultsDocsSection.display.connect('activated', function(resultsDocsDisplay) {
- me.emit('activated');
- });
- this._docDisplay.connect('selected', Lang.bind(this, function(docDisplay) {
- this._resultsDocsSection.display.unsetSelected();
- this._resultsAppsSection.display.unsetSelected();
- this._showDetails();
- this._detailsContent.remove_all();
- this._detailsContent.append(this._docDisplay.selectedItemDetails, Big.BoxPackFlags.NONE);
- }));
- this._docDisplay.connect('toggle-details', Lang.bind(this, function(docDisplay) {
- this._toggleDetails();
- }));
- this._resultsDocsSection.display.connect('selected', Lang.bind(this, function(resultsDocDisplay) {
- this._docDisplay.unsetSelected();
- this._resultsAppsSection.display.unsetSelected();
- this._showDetails();
- this._detailsContent.remove_all();
- this._detailsContent.append(this._resultsDocsSection.display.selectedItemDetails, Big.BoxPackFlags.NONE);
- }));
- this._resultsDocsSection.display.connect('toggle-details', Lang.bind(this, function(resultsDocDisplay) {
- this._toggleDetails();
- }));
- this._resultsAppsSection.display.connect('selected', Lang.bind(this, function(resultsAppDisplay) {
- this._docDisplay.unsetSelected();
- this._resultsDocsSection.display.unsetSelected();
- this._showDetails();
- this._detailsContent.remove_all();
- this._detailsContent.append(this._resultsAppsSection.display.selectedItemDetails, Big.BoxPackFlags.NONE);
- }));
-
- this._moreAppsLink.connect('clicked',
- function(o, event) {
- if (me._moreAppsMode) {
- me._unsetMoreAppsMode();
- } else {
- me._setMoreAppsMode();
- }
- });
-
- this._moreDocsLink.connect('clicked',
- function(o, event) {
- if (me._moreDocsMode) {
- me._unsetMoreDocsMode();
- } else {
- me._setMoreDocsMode();
- }
- });
- },
-
- show: function() {
let global = Shell.Global.get();
- this._appsContent.show();
- this._docDisplay.show();
- global.stage.set_key_focus(this._searchEntry.entry);
- },
-
- hide: function() {
- this._firstSelectAfterOverlayShow = true;
- this._appsContent.hide();
- this._docDisplay.hide();
- this._searchEntry.entry.text = '';
- this.unsetMoreMode();
- },
-
- unsetMoreMode: function() {
- this._unsetMoreAppsMode();
- this._unsetMoreDocsMode();
- if (this._detailsShowing()) {
- this._detailsPane.hide();
- this.emit('panes-removed');
- }
- this._unsetSearchMode();
- },
-
- // Sets the 'More' mode for browsing applications.
- _setMoreAppsMode: function() {
- if (this._moreAppsMode)
- return;
-
- this._unsetMoreDocsMode();
- this._unsetSearchMode();
- this._moreAppsMode = true;
-
- this._resultsAppsSection.display.show();
- this._resultsPane.append(this._resultsAppsSection.actor, Big.BoxPackFlags.EXPAND);
- this._resultsPane.show();
- this._repositionDetails();
-
- this._moreAppsLink.setText("Less...");
- this.emit('panes-displayed');
- },
-
- // Unsets the 'More' mode for browsing applications.
- _unsetMoreAppsMode: function() {
- if (!this._moreAppsMode)
- return;
-
- this._moreAppsMode = false;
-
- this._resultsPane.remove_actor(this._resultsAppsSection.actor);
- this._resultsAppsSection.display.hide();
- this._resultsPane.hide();
-
- this._moreAppsLink.setText("More...");
-
- this._hideDetails();
- },
-
- // Sets the 'More' mode for browsing documents.
- _setMoreDocsMode: function() {
- if (this._moreDocsMode)
- return;
-
- this._unsetMoreAppsMode();
- this._unsetSearchMode();
- this._moreDocsMode = true;
-
- this._resultsDocsSection.display.show();
- this._resultsPane.append(this._resultsDocsSection.actor, Big.BoxPackFlags.EXPAND);
- this._resultsPane.show();
- this._repositionDetails();
-
- this._moreDocsLink.setText("Less...");
-
- this.emit('panes-displayed');
- },
-
- // Unsets the 'More' mode for browsing documents.
- _unsetMoreDocsMode: function() {
- if (!this._moreDocsMode)
- return;
-
- this._moreDocsMode = false;
-
- this._resultsPane.hide();
- this._resultsPane.remove_actor(this._resultsDocsSection.actor);
- this._resultsDocsSection.display.hide();
-
- this._moreDocsLink.setText("More...");
-
- this._hideDetails();
- },
-
- _setSearchMode: function() {
-
- if (this._resultsShowing())
- return;
-
- this._resultsAppsSection._setSearchMode();
- this._resultsAppsSection.display.show();
- this._resultsPane.append(this._resultsAppsSection.actor, Big.BoxPackFlags.EXPAND);
-
- this._resultsDocsSection._setSearchMode();
- this._resultsDocsSection.display.show();
- this._resultsPane.append(this._resultsDocsSection.actor, Big.BoxPackFlags.EXPAND);
-
- this._resultsPane.show();
- this._repositionDetails();
+ this._group = new Clutter.Group();
+ this._group._delegate = this;
- this.emit('panes-displayed');
- },
+ this.visible = false;
+ this._hideInProgress = false;
- _unsetSearchMode: function() {
+ this._recalculateGridSizes();
- if (this._moreDocsMode || this._moreAppsMode || !this._resultsShowing())
- return;
+ // A scaled root pixmap actor is used as a background. It is zoomed in
+ // to the lower right intersection of the lines that divide the image
+ // evenly in a 3x3 grid. This is based on the rule of thirds, a
+ // compositional rule of thumb in visual arts. The choice for the
+ // lower right point is based on a quick survey of GNOME wallpapers.
+ this._background = global.create_root_pixmap_actor();
+ this._group.add_actor(this._background);
- this._resultsPane.hide();
- this._repositionDetails();
+ this._activeDisplayPane = null;
- this._resultsPane.remove_actor(this._resultsAppsSection.actor);
- this._resultsAppsSection.display.hide();
- this._resultsAppsSection._unsetSearchMode();
+ // Used to catch any clicks when we have an active pane; see the comments
+ // in addPane below.
+ this._transparentBackground = new Clutter.Rectangle({ opacity: 0,
+ reactive: true });
+ this._group.add_actor(this._transparentBackground);
- this._resultsPane.remove_actor(this._resultsDocsSection.actor);
- this._resultsDocsSection.display.hide();
- this._resultsDocsSection._unsetSearchMode();
- this._resultsDocsSection.actor.set_y(0);
+ // Draw a semitransparent rectangle over the background for readability.
+ this._backOver = new Clutter.Rectangle({ color: ROOT_OVERLAY_COLOR });
+ this._group.add_actor(this._backOver);
- this._hideDetails();
- },
+ this._group.hide();
+ global.overlay_group.add_actor(this._group);
- _repositionDetails: function () {
- let x;
- if (this._resultsPane.visible)
- x = this._resultsPane.x + this._resultsPane.width;
- else
- x = this._width;
- this._detailsPane.x = x;
- },
+ // TODO - recalculate everything when desktop size changes
+ this._dash = new Dash.Dash(displayGridColumnWidth);
+ this._group.add_actor(this._dash.actor);
- _showDetails: function () {
- this._detailsPane.show();
- this._repositionDetails();
- this.emit('panes-displayed');
- },
+ // Container to hold popup pane chrome.
+ this._paneContainer = new Big.Box({ orientation: Big.BoxOrientation.HORIZONTAL,
+ spacing: 6
+ });
+ // Note here we explicitly don't set the paneContainer to be reactive yet; that's done
+ // inside the notify::visible handler on panes.
+ this._paneContainer.connect('button-release-event', Lang.bind(this, function(background) {
+ this._activeDisplayPane.close();
+ return true;
+ }));
+ this._group.add_actor(this._paneContainer);
- _hideDetails: function() {
- if (!this._detailsShowing)
- return;
- this._detailsPane.hide();
- this.emit('panes-removed');
- },
+ this._transparentBackground.lower_bottom();
+ this._paneContainer.lower_bottom();
- _toggleDetails: function() {
- if (this._detailsShowing())
- this._hideDetails();
- else
- this._showDetails();
- },
+ this._repositionChildren();
- _detailsShowing: function() {
- return this._detailsPane.visible;
+ this._workspaces = null;
},
- _resultsShowing: function() {
- return this._resultsPane.visible;
- }
-};
-
-Signals.addSignalMethods(Dash.prototype);
-
-function Overlay() {
- this._init();
-}
-
-Overlay.prototype = {
- _init : function() {
- let me = this;
-
+ _recalculateGridSizes: function () {
let global = Shell.Global.get();
wideScreen = (global.screen_width/global.screen_height > WIDE_SCREEN_CUT_OFF_RATIO);
// We divide the screen into an imaginary grid which helps us determine the layout of
- // different visual components.
+ // different visual components.
if (wideScreen) {
displayGridColumnWidth = global.screen_width / COLUMNS_WIDE_SCREEN;
displayGridRowHeight = global.screen_height / ROWS_WIDE_SCREEN;
displayGridColumnWidth = global.screen_width / COLUMNS_REGULAR_SCREEN;
displayGridRowHeight = global.screen_height / ROWS_REGULAR_SCREEN;
}
+ },
- this._group = new Clutter.Group();
- this._group._delegate = this;
+ _repositionChildren: function () {
+ let global = Shell.Global.get();
- this.visible = false;
- this._hideInProgress = false;
+ let contentHeight = global.screen_height - Panel.PANEL_HEIGHT;
- // A scaled root pixmap actor is used as a background. It is zoomed in
- // to the lower right intersection of the lines that divide the image
- // evenly in a 3x3 grid. This is based on the rule of thirds, a
- // compositional rule of thumb in visual arts. The choice for the
- // lower right point is based on a quick survey of GNOME wallpapers.
- let background = global.create_root_pixmap_actor();
- background.width = global.screen_width * BACKGROUND_SCALE;
- background.height = global.screen_height * BACKGROUND_SCALE;
- background.x = -global.screen_width * (4 * BACKGROUND_SCALE - 3) / 6;
- background.y = -global.screen_height * (4 * BACKGROUND_SCALE - 3) / 6;
- this._group.add_actor(background);
-
- // Transparent background is used to catch clicks outside of the dash panes when the panes
- // are being displayed and the workspaces area should not be reactive. Catching such a
- // click results in the panes being closed and the workspaces area becoming reactive again.
- this._transparentBackground = new Clutter.Rectangle({ opacity: 0,
- width: global.screen_width,
- height: global.screen_height - Panel.PANEL_HEIGHT,
- y: Panel.PANEL_HEIGHT,
- reactive: true });
- this._group.add_actor(this._transparentBackground);
+ this._dash.actor.set_position(0, Panel.PANEL_HEIGHT);
+ this._dash.actor.set_size(displayGridColumnWidth, contentHeight);
- // Draw a semitransparent rectangle over the background for readability.
- let backOver = new Clutter.Rectangle({ color: ROOT_OVERLAY_COLOR,
- width: global.screen_width,
- height: global.screen_height - Panel.PANEL_HEIGHT,
- y: Panel.PANEL_HEIGHT });
- this._group.add_actor(backOver);
+ this._backOver.set_position(0, Panel.PANEL_HEIGHT);
+ this._backOver.set_size(global.screen_width, contentHeight);
- this._group.hide();
- global.overlay_group.add_actor(this._group);
+ let bgPositionFactor = (4 * BACKGROUND_SCALE - 3) / 6;
+ this._background.set_size(global.screen_width * BACKGROUND_SCALE,
+ global.screen_height * BACKGROUND_SCALE);
+ this._background.set_position(-global.screen_width * bgPositionFactor,
+ -global.screen_height * bgPositionFactor);
- // TODO - recalculate everything when desktop size changes
- this._dash = new Dash();
- this._group.add_actor(this._dash.actor);
- this._workspaces = null;
- this._buttonEventHandlerId = null;
- this._dash.connect('activated', function(dash) {
- // TODO - have some sort of animation/effect while
- // transitioning to the new app. We definitely need
- // startup-notification integration at least.
- me.hide();
- });
- this._dash.connect('panes-displayed', function(dash) {
- if (me._buttonEventHandlerId == null) {
- me._transparentBackground.raise_top();
- me._dash.actor.raise_top();
- me._buttonEventHandlerId = me._transparentBackground.connect('button-release-event', function(background) {
- me._dash.unsetMoreMode();
+ this._paneContainer.set_position(this._dash.actor.x + this._dash.actor.width + DEFAULT_PADDING,
+ Panel.PANEL_HEIGHT);
+ // Dynamic width
+ this._paneContainer.height = contentHeight;
+
+ this._transparentBackground.set_position(this._paneContainer.x, this._paneContainer.y);
+ this._transparentBackground.set_size(global.screen_width - this._paneContainer.x,
+ this._paneContainer.height);
+ },
+
+ addPane: function (pane) {
+ pane.actor.width = displayGridColumnWidth * 2;
+ this._paneContainer.append(pane.actor, Big.BoxPackFlags.NONE);
+ // When a pane is displayed, we raise the transparent background to the top
+ // and connect to button-release-event on it, then raise the pane above that.
+ // The idea here is that clicking anywhere outside the pane should close it.
+ // When the active pane is closed, undo the effect.
+ let backgroundEventId = null;
+ pane.connect('open-state-changed', Lang.bind(this, function (pane, isOpen) {
+ if (isOpen) {
+ this._activeDisplayPane = pane;
+ this._transparentBackground.raise_top();
+ this._paneContainer.raise_top();
+ if (backgroundEventId != null)
+ this._transparentBackground.disconnect(backgroundEventId);
+ backgroundEventId = this._transparentBackground.connect('button-release-event', Lang.bind(this, function () {
+ this._activeDisplayPane.close();
return true;
- });
- }
- });
- this._dash.connect('panes-removed', function(dash) {
- if (me._buttonEventHandlerId != null) {
- me._transparentBackground.lower_bottom();
- me._transparentBackground.disconnect(me._buttonEventHandlerId);
- me._buttonEventHandlerId = null;
+ }));
+ } else if (pane == this._activeDisplayPane) {
+ this._activeDisplayPane = null;
+ if (backgroundEventId != null) {
+ this._transparentBackground.disconnect(backgroundEventId);
+ backgroundEventId = null;
+ }
+ this._transparentBackground.lower_bottom();
+ this._paneContainer.lower_bottom();
}
- });
+ }));
},
//// Draggable target interface ////
- // Unsets the expanded display mode if a GenericDisplayItem is being
+ // Closes any active panes if a GenericDisplayItem is being
// dragged over the overlay, i.e. as soon as it starts being dragged.
- // This closes the additional panes and allows the user to place
- // the item on any workspace.
+ // This allows the user to place the item on any workspace.
handleDragOver : function(source, actor, x, y, time) {
- if (source instanceof GenericDisplay.GenericDisplayItem) {
- this._dash.unsetMoreMode();
+ if (source instanceof GenericDisplay.GenericDisplayItem
+ || source instanceof AppDisplay.WellDisplayItem) {
+ if (this._activeDisplayPane != null)
+ this._activeDisplayPane.close();
return true;
}
-
+
return false;
},
let global = Shell.Global.get();
this._hideInProgress = true;
- // lower the Dash, so that workspaces display is on top and covers the Dash while it is sliding out
+ if (this._activeDisplayPane != null)
+ this._activeDisplayPane.close();
+ // lower the panes, so that workspaces display is on top while sliding out
this._dash.actor.lower(this._workspaces.actor);
this._workspaces.hide();